package de.mmis.core.base.abstracttree.serializer.selector;

import java.util.Map;

import de.mmis.core.base.abstracttree.LeafNode;
import de.mmis.core.base.abstracttree.Tree;
import de.mmis.core.base.gclass.GClass;
import de.mmis.core.base.gclass.GParameterized;

/**
 * {@link InstanceSelector} representing an abstract {@link Number} type. The
 * target type determines the type of the {@link Number} (int, double, float,
 * ...)
 * 
 * @author Christian Koch
 */
public class NumberSelector extends InstanceSelector {

	private Number number;
	private Character modifier;
	private LeafNode atom;

	public NumberSelector(LeafNode atom, Number number, Character modifier,
			String id, Map<String, Object> idmap) {
		super(id, idmap);
		this.atom = atom;
		this.number = number;
		this.modifier = modifier;
	}

	private InstanceSelectorException generateModifierException(GClass gclass) {
		return new InstanceSelectorException("modifier '" + this.modifier
				+ "' prevents number \"" + this.number
				+ "\" from being converted to type " + gclass);
	}

	@Override
	public Object createInstance(GClass gclass)
			throws InstanceSelectorException {

		Object ret = createInstanceInternal(gclass);
		setID(ret);
		return ret;
	}

	private Byte toByte() throws InstanceSelectorException {
		if ((this.number instanceof Double) || (this.number instanceof Float))
			throw new InstanceSelectorException(
					"number "
							+ this.number
							+ " cannot be converted to type Byte as it is not an integer");

		if (Byte.MIN_VALUE > this.number.longValue()
				|| Byte.MAX_VALUE < this.number.longValue())
			throw new InstanceSelectorException(
					"number "
							+ this.number
							+ " cannot be converted to type Byte as it is out of bounds");

		return new Byte(this.number.byteValue());
	}

	private Short toShort() throws InstanceSelectorException {
		if ((this.number instanceof Double) || (this.number instanceof Float))
			throw new InstanceSelectorException(
					"number "
							+ this.number
							+ " cannot be converted to type Short as it is not an integer");

		if (Short.MIN_VALUE > this.number.longValue()
				|| Short.MAX_VALUE < this.number.longValue())
			throw new InstanceSelectorException(
					"number "
							+ this.number
							+ " cannot be converted to type Short as it is out of bounds");

		return new Short(this.number.shortValue());
	}

	private Integer toInteger() throws InstanceSelectorException {
		if ((this.number instanceof Double) || (this.number instanceof Float))
			throw new InstanceSelectorException(
					"number "
							+ this.number
							+ " cannot be converted to type Integer as it is not an integer");

		if (Integer.MIN_VALUE > this.number.longValue()
				|| Integer.MAX_VALUE < this.number.longValue())
			throw new InstanceSelectorException(
					"number "
							+ this.number
							+ " cannot be converted to type Integer as it is out of bounds");

		return new Integer(this.number.intValue());
	}

	private Long toLong() throws InstanceSelectorException {
		if ((this.number instanceof Double) || (this.number instanceof Float))
			throw new InstanceSelectorException(
					"number "
							+ this.number
							+ " cannot be converted to type Long as it is not an integer");

		return new Long(this.number.longValue());
	}

	private Object createInstanceInternal(GClass gclass)
			throws InstanceSelectorException {
		if (gclass instanceof GParameterized) {
			Class<?> base = ((GParameterized) gclass).getBaseClass();

			if (base == byte.class || base == Byte.class) {
				if (this.modifier != null && this.modifier.charValue() != 'b'
						&& this.modifier.charValue() != 'B')
					throw generateModifierException(gclass);
				return toByte();
			}
			if (base == short.class || base == Short.class) {
				if (this.modifier != null && this.modifier.charValue() != 's'
						&& this.modifier.charValue() != 'S')
					throw generateModifierException(gclass);
				return toShort();
			}
			if (base == int.class || base == Integer.class) {
				if (this.modifier != null && this.modifier.charValue() != 'i'
						&& this.modifier.charValue() != 'I')
					throw generateModifierException(gclass);
				return toInteger();
			}
			if (base == long.class || base == Long.class) {
				if (this.modifier != null && this.modifier.charValue() != 'l'
						&& this.modifier.charValue() != 'L')
					throw generateModifierException(gclass);
				return toLong();
			}
			if (base == float.class || base == Float.class) {
				if (this.modifier != null && this.modifier.charValue() != 'f'
						&& this.modifier.charValue() != 'F')
					throw generateModifierException(gclass);
				return new Float(this.number.floatValue());
			}
			if (base == double.class || base == Double.class) {
				if (this.modifier != null && this.modifier.charValue() != 'd'
						&& this.modifier.charValue() != 'D')
					throw generateModifierException(gclass);
				return new Double(this.number.doubleValue());
			}

			if (base == Number.class || base == Object.class) {
				if (this.modifier == null) {
					if (this.number instanceof Float || this.number instanceof Double)
						return new Double(this.number.doubleValue());

					return toInteger();
				}

				if (this.modifier.charValue() == 'b'
						|| this.modifier.charValue() == 'B') {
					return toByte();
				}
				if (this.modifier.charValue() == 's'
						|| this.modifier.charValue() == 'S') {
					return toShort();
				}
				if (this.modifier.charValue() == 'i'
						|| this.modifier.charValue() == 'I') {
					return toInteger();
				}
				if (this.modifier.charValue() == 'l'
						|| this.modifier.charValue() == 'L') {
					return toLong();
				}
				if (this.modifier.charValue() == 'f'
						|| this.modifier.charValue() == 'F') {
					return new Float(this.number.floatValue());
				}
				if (this.modifier.charValue() == 'd'
						|| this.modifier.charValue() == 'D') {
					return new Double(this.number.doubleValue());
				}

				throw new InstanceSelectorException("unknown modifier '"
						+ this.modifier + "': number \"" + this.number + "\""
						+ " cannot be converted to type " + gclass);
			}

			if (Tree.class.isAssignableFrom(base)) {
				return this.atom;
			}
		}

		throw new InstanceSelectorException("number \""
				+ this.number
				+ "\""
				+ (this.modifier == null ? "" : " (modifier: " + this.modifier
						+ ")") + " cannot be converted to type " + gclass);
	}
}
